import type { Node } from "./node.ts";
import { immerable } from "immer";
import type { Schema } from "./schema.ts";
/**
 * 顶级 Fragment默认只有数组 片段
 */
export class Fragment {
  [immerable] = true;
  content: Node[];
  constructor(content: Node[]) {
    this.content = content;
  }
  get childCount(): number {
    return this.content.length;
  }
  nodeAt(id: string): Node | null {
    for (let i = 0; i < this.content.length; i++) {
      const child: Node = this.content[i];
      if (child) {
        const found = child.childCount ? child.nodeAt(id) : child;
        if (found) return found;
      }
    }
    return null;
  }
  child(index: number): Node {
    const found = this.content[index];
    if (!found) {
      throw new RangeError("Index " + index + " out of range for " + this);
    }
    return found;
  }
  forEach(
    callbackfn: (value: Node, index: number, array: Node[]) => void | boolean,
  ) {
    this.content.forEach((value, index, array) => {
      const result = callbackfn(value, index, array);
      if (result !== false && value.childCount) {
        value.forEach(callbackfn);
      }
    });
  }
  toJSON(): any {
    return this.content.length ? this.content.map((n) => n.toJSON()) : null;
  }
  toString(): string {
    return "<" + this.toStringInner() + ">";
  }
  toStringInner(): string {
    return this.content.join(", ");
  }
  static from(nodes?: Fragment | Node | readonly Node[] | null): Fragment {
    if (!nodes) return Fragment.empty;
    if (nodes instanceof Fragment) return nodes;
    if (Array.isArray(nodes)) return this.fromArray(nodes);
    if ((nodes as Node).attrs) return new Fragment([nodes as Node]);
    throw new RangeError(
      "Can not convert " + nodes + " to a Fragment" +
        ((nodes as any).nodesBetween
          ? " (looks like multiple versions of prosemirror-model were loaded)"
          : ""),
    );
  }
  static fromArray(array: Node[]): Fragment {
    if (!array.length) return Fragment.empty;
    let joined: Node[] | undefined;
    for (let i = 0; i < array.length; i++) {
      const node = array[i];
      if (joined) {
        joined.push(node);
      }
    }
    return new Fragment(joined || array);
  }
  static fromJSON(schema: Schema, value: any): Fragment {
    if (!value) return Fragment.empty;
    if (!Array.isArray(value)) {
      throw new RangeError("Invalid input for Fragment.fromJSON");
    }
    return new Fragment(value.map(schema.nodeFromJSON));
  }
  static empty: Fragment = new Fragment([]);
}
